跳到主要内容

GLFW 库

GLFW 库是什么?

GLFW 是配合 OpenGL 使用的轻量级工具程序库,GLFW 的主要功能是创建并管理窗口和 OpenGL 上下文,同时还提供了处理手柄、键盘、鼠标输入的功能。

Windows 下的环境配置

首先在 MSYS2 官网下载安装包,这个 MSYS2 是一个 Windows 下模拟 Linux 的包管理器,可以用来执行 Linux 的包,安装好后找到在 Windows 的 MSYS2 安装目录里的 /etc/pacman.d 下面直接编辑文本

编辑 /etc/pacman.d/mirrorlist.mingw32,在文件开头添加:

Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/mingw/i686

编辑 /etc/pacman.d/mirrorlist.mingw64,在文件开头添加:

Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/mingw/x86_64

编辑 /etc/pacman.d/mirrorlist.msys,在文件开头添加:

Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/msys/$arch

然后管理员权限执行 pacman -Sy 刷新软件包数据即可。

然后是本次安装的重头戏,gcc 编译工具

pacman -S --needed base-devel mingw-w64-i686-toolchain mingw-w64-x86_64-toolchain

# 如果安装失败可以执行下面的命令手动安装
# 查询并找到 msys/gcc
pacman -Ss gcc
# 安装
pacman -S msys/gcc

安装完毕后,需要修改一下 MSYS2 的环境变量,找到 MSYS2 安装目录下的 msys2_shell.cmd

在 15 行附近增加如下两行

set MSYSTEM=MINGW64
set MSYS2_PATH_TYPE=inherit

管理员运行 msys2_shell.cmd,执行 gcc -–version 并看到有输出,说明编译环境安装完毕

Linux 配置环境

如果是 Ubuntu 下安装环境,只需下面一条命令就行了

sudo apt install pkg-config gcc libgl1-mesa-dev xorg-dev

配置 GLFW 环境

开始安装 Golang 依赖(如果使用 MSYS2 则是在它的终端执行)

下面提供了两个版本的 OpenGL 版本选择,这里使用 4.1 版本

# For OpenGL 4.1
$ go get github.com/go-gl/gl/v4.1-core/gl

# Or 2.1
$ go get github.com/go-gl/gl/v2.1/gl

然后安装 GLFW

go get -u github.com/go-gl/glfw/v3.2/glfw

安装 UI 库 Nuklear,这个 UI 库是一个 C 编写的、只有头文件的 GUI 界面库,也是 imgui 类的界面实现工具,支持多种渲染后端,包括 gdi/gdi+/D3D/X11/Xft/SDL/opengl 等,特别适合跨平台和工具软件的界面实现。

go get -v github.com/golang-ui/nuklear/nk

对上述安装好的组件进行测试,下载 Nuklear 测试程序

go get github.com/golang-ui/nuklear/cmd/nk-example

执行测试

nk-example

如果出现下面界面,环境搭建部分就大功告成啦

开始编写窗口

安装了个软件包之后,定义 main 函数,用于初始化 OpenGL 和 GLFW 并显示窗口:

注:如果在 Windows 上需要在 MSYS2 里面启动

package main

import (
"log"
"runtime"

"github.com/go-gl/gl/v3.3-core/gl"
"github.com/go-gl/glfw/v3.2/glfw"
)

const (
WIDTH = 500
HEIGHT = 500
)

// initGlfw initializes glfw and returns a Window to use.
func initGlfw() *glfw.Window {
if err := glfw.Init(); err != nil {
panic(err)
}

// 通过这些枚举值来设置 GLFW 的参数
glfw.WindowHint(glfw.Resizable, glfw.False) // 设置窗口大小无法修改
glfw.WindowHint(glfw.ContextVersionMajor, 3) // OpenGL最大版本
glfw.WindowHint(glfw.ContextVersionMinor, 3) // OpenGl 最小版本
glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) // 明确核心模式
glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True) // Mac使用时需要加上

window, err := glfw.CreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nil, nil)
log.Println("created window")
if window == nil || err != nil {
panic(err)
}
return window
}

func main() {
runtime.LockOSThread()
window := initGlfw()
defer glfw.Terminate()
window.MakeContextCurrent() // 通知 glfw 将当前窗口上下文设置为线程主上下文

// 别忘了初始化 OpenGL
if err := gl.Init(); err != nil {
panic(err)
}

// 必须告诉 OpenGL 渲染窗口的尺寸大小,即视口(Viewport),这样 OpenGL 才只能知道怎样根据窗口大小显示数据和坐标。
gl.Viewport(0, 0, WIDTH, HEIGHT) // 起点为左下角

// 窗口大小被改变的回调函数
window.SetFramebufferSizeCallback(framebuffer_size_callback)

//渲染循环
for !window.ShouldClose() {
//用户输入
processInput(window)

//渲染
gl.ClearColor(0.2, 0.3, 0.3, 1.0) //状态设置
gl.Clear(gl.COLOR_BUFFER_BIT) //状态使用

//检查调用事件,交换缓冲
glfw.PollEvents()
window.SwapBuffers()
}
}

func processInput(window *glfw.Window) {
if window.GetKey(glfw.KeyEscape) == glfw.Press {
log.Println("escape pressed")
window.SetShouldClose(true)
}
}

// 在调整指定窗口的帧缓冲区大小时调用。
func framebuffer_size_callback(window *glfw.Window, width int, height int) {
log.Printf("resize width:%d, height:%d", width, height)
gl.Viewport(0, 0, int32(width), int32(height))
}

执行后就可以看到生成的窗口了

下面来分别介绍下上面的代码的含义

初始化 GLFW

首先是初始化一个 GLFW,它代表一个窗口

func initGlfw() *glfw.Window {
if err := glfw.Init(); err != nil {
panic(err)
}

// 通过这些枚举值来设置 GLFW 的参数
glfw.WindowHint(glfw.Resizable, glfw.False) // 设置窗口大小无法修改
glfw.WindowHint(glfw.ContextVersionMajor, 3) // OpenGL最大版本
glfw.WindowHint(glfw.ContextVersionMinor, 3) // OpenGl 最小版本
glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) // 明确核心模式
glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True) // Mac使用时需要加上

window, err := glfw.CreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nil, nil)
log.Println("created window")
if window == nil || err != nil {
panic(err)
}
return window
}

这一步可以通过 glfw 包提供的枚举值来设置 Window 的属性,可以在 这里 找到 glfw 提供的全部配置项,这些配置的使用说明参考 GLFW’s window handling

这里挑几个常用的配置说明:

GLFW_RESIZABLE       指定用户是否可以调整窗口的大小
GLFW_DECORATED 指定窗口模式窗口是否具有窗口装饰,如边框、关闭的小部件等。
GLFW_FOCUSED 指定窗口模式窗口在创建时是否给予输入焦点
GLFW_AUTO_ICONIFY 指定当输入焦点丢失时,全屏窗口是否会自动将以前的视频模式图标化并恢复
GLFW_FLOATING 指定窗口模式窗口是否将浮动在其他常规窗口之上,也称为最顶端窗口或总是在顶端窗口
GLFW_MAXIMIZED 指定窗口模式窗口在创建时是否最大化
GLFW_CENTER_CURSOR 指定光标是否应该位于新创建的全屏窗口的中央

如果使用的是 MacOS X 系统需要加上 OpenGLForwardCompatible 配置

绑定上下文及初始化 GL

runtime.LockOSThread()
window := initGlfw()
defer glfw.Terminate()
window.MakeContextCurrent() // 通知 glfw 将当前窗口上下文设置为线程主上下文

// 别忘了初始化 OpenGL
if err := gl.Init(); err != nil {
panic(err)
}

// 必须告诉 OpenGL 渲染窗口的尺寸大小,即视口(Viewport),这样 OpenGL 才只能知道怎样根据窗口大小显示数据和坐标。
gl.Viewport(0, 0, WIDTH, HEIGHT) // 起点为左下角

// 窗口大小被改变的回调函数
window.SetFramebufferSizeCallback(framebuffer_size_callback)

注意,第一行使用 runtime.LockOSThread() 确保整个程序总是在同一个操作系统线程中执行,这对于必须总是从初始化时使用的同一个线程调用 GLFW 非常重要。

循环渲染画面

我们可不希望只绘制一个图像之后我们的应用程序就立即退出并关闭窗口。我们希望程序在我们主动关闭它之前不断绘制图像并能够接受用户输入。因此,我们需要在程序中添加一个 while 循环,我们可以把它称之为渲染循环 Render Loop,它能在我们让 GLFW 退出前一直保持运行。

下面几行的代码就实现了一个简单的渲染循环:

//渲染循环
for !window.ShouldClose() {
//用户输入
processInput(window)
//渲染
gl.ClearColor(0.2, 0.3, 0.3, 1.0) //状态设置
gl.Clear(gl.COLOR_BUFFER_BIT) //状态使用
//检查调用事件,交换缓冲
glfw.PollEvents()
window.SwapBuffers()
}

这里的 window.ShouldClose() 在每次循环的开始前检查一次 GLFW 是否被要求退出,如果是的话该函数返回 true 然后渲染循环便结束了,之后为我们就可以关闭应用程序了。

这里的 glfw.PollEvents() 用来检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数(可以通过回调方法手动设置)。

最后的 window.SwapBuffers() 会交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。

References

OpenGL & Go Tutorial Part 1: Hello, OpenGL learnopengl Golang OpenGL 跨平台图形程序开发(1)技术选型和windows环境搭建